<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Keymaster unit tests</title>
  <script src="../keymaster.js"></script>
  <script src="evidence.js"></script>
</head>
<body>
  <h1>Keymaster unit tests</h1>
  <p>
    See the browser console for results.
  </p>

  <input type="text" id="input_text">
  <textarea id="textarea"></textarea>
  <select id="select"></select>

  <script>
    function $(id){
      return document.getElementById(id);
    }

    var modifierMap = {
      16:'shiftKey',
      18:'altKey',
      17:'ctrlKey',
      91:'metaKey'
    };

    // because the DOM is retarded,
    // and browsers don't really care about the DOM API anyway
    // (IE, Firefox, WebKit are all using different event generators),
    // we'll just simulate events roughly
    function keydown(code, modifiers, el){
      var event = document.createEvent('Event');
      event.initEvent('keydown', true, true);
      event.keyCode = code;
      if (modifiers && modifiers.length > 0)
          for(i in modifiers) event[modifierMap[modifiers[i]]] = true;
      (el||document).dispatchEvent(event);
    }

    function keyup(code, el){
      var event = document.createEvent('Event');
      event.initEvent('keyup', true, true);
      event.keyCode = code;
      (el||document).dispatchEvent(event);
    }

    var KEYS = {
      '⇧': 16, shift: 16,
      '⌥': 18, alt: 18, option: 18,
      '⌃': 17, ctrl: 17, control: 17,
      '⌘': 91, command: 91
    };

    Evidence.TestCase.extend('KeymasterTest', {
      testShortcut: function(t){
        var cnt = 0;
        key('a', function(){ cnt++ });
        keydown(65);
        keyup(65);
        t.assertEqual(1, cnt);
        keydown(65);
        keyup(65);
        t.assertEqual(2, cnt);
      },

      testUnbind: function(t){
        var cnt = 0;
        key('a', function(){ cnt++ });
        keydown(65);
        keyup(65);
        t.assertEqual(1, cnt);
        key.unbind('a');
        keydown(65);
        keyup(65);
        t.assertEqual(1, cnt);
      },

      testShortcutWithModifiers: function(t){
        var cntA = 0, cntShiftA = 0, cntCtrlShiftA = 0, cntCommandCtrlShiftA = 0, cntCommandCtrlAltShiftA = 0;
        key('a', function(){ cntA++ });
        key('shift+a', function(){ cntShiftA++ });
        key('ctrl+shift+a', function(){ cntCtrlShiftA++ });
        key('command+ctrl+shift+a', function(){ cntCommandCtrlShiftA++ });
        key('command+ctrl+alt+shift+a', function(){ cntCommandCtrlAltShiftA++ });

        keydown(65); keyup(65);
        t.assertEqual(1, cntA);
        t.assertEqual(0, cntShiftA);
        t.assertEqual(0, cntCtrlShiftA);
        t.assertEqual(0, cntCommandCtrlShiftA);
        t.assertEqual(0, cntCommandCtrlAltShiftA);

        keydown(KEYS.shift); keydown(65, [16]); keyup(65); keyup(KEYS.shift);
        t.assertEqual(1, cntA);
        t.assertEqual(1, cntShiftA);
        t.assertEqual(0, cntCtrlShiftA);
        t.assertEqual(0, cntCommandCtrlShiftA);
        t.assertEqual(0, cntCommandCtrlAltShiftA);

        keydown(KEYS.shift); keydown(KEYS.ctrl); keydown(65, [16, 17]); keyup(65); keyup(KEYS.shift); keyup(KEYS.ctrl);
        t.assertEqual(1, cntA);
        t.assertEqual(1, cntShiftA);
        t.assertEqual(1, cntCtrlShiftA);
        t.assertEqual(0, cntCommandCtrlShiftA);
        t.assertEqual(0, cntCommandCtrlAltShiftA);

        keydown(KEYS.command); keydown(KEYS.shift); keydown(KEYS.ctrl);
        keydown(65, [91, 16, 17]); keyup(65);
        keyup(KEYS.shift); keyup(KEYS.ctrl); keyup(KEYS.command);
        t.assertEqual(1, cntA);
        t.assertEqual(1, cntShiftA);
        t.assertEqual(1, cntCtrlShiftA);
        t.assertEqual(1, cntCommandCtrlShiftA);
        t.assertEqual(0, cntCommandCtrlAltShiftA);

        keydown(KEYS.alt); keydown(KEYS.command); keydown(KEYS.shift); keydown(KEYS.ctrl);
        keydown(65, [18, 91, 16, 17]); keyup(65);
        keyup(KEYS.shift); keyup(KEYS.ctrl); keyup(KEYS.command); keyup(KEYS.alt);
        t.assertEqual(1, cntA);
        t.assertEqual(1, cntShiftA);
        t.assertEqual(1, cntCtrlShiftA);
        t.assertEqual(1, cntCommandCtrlShiftA);
        t.assertEqual(1, cntCommandCtrlAltShiftA);
      },

      testUnbindWithModifiers: function(t){
        var cntA = 0, cntShiftA = 0, cntCtrlShiftA = 0, cntCommandCtrlShiftA = 0, cntCommandCtrlAltShiftA = 0;
        key('a', function(){ cntA++ });
        key('shift+a', function(){ cntShiftA++ });
        key('ctrl+shift+a', function(){ cntCtrlShiftA++ });
        key('command+ctrl+shift+a', function(){ cntCommandCtrlShiftA++ });
        key('command+ctrl+alt+shift+a', function(){ cntCommandCtrlAltShiftA++ });

        keydown(65); keyup(65);
        keydown(KEYS.shift); keydown(65, [16]); keyup(65); keyup(KEYS.shift);
        keydown(KEYS.shift); keydown(KEYS.ctrl); keydown(65, [16, 17]); keyup(65); keyup(KEYS.shift); keyup(KEYS.ctrl);
        keydown(KEYS.command); keydown(KEYS.shift); keydown(KEYS.ctrl);
        keydown(65, [91, 16, 17]); keyup(65);
        keyup(KEYS.shift); keyup(KEYS.ctrl); keyup(KEYS.command);
        keydown(KEYS.alt); keydown(KEYS.command); keydown(KEYS.shift); keydown(KEYS.ctrl);
        keydown(65, [18, 91, 16, 17]); keyup(65);
        keyup(KEYS.shift); keyup(KEYS.ctrl); keyup(KEYS.command); keyup(KEYS.alt);

        t.assertEqual(1, cntA);
        t.assertEqual(1, cntShiftA);
        t.assertEqual(1, cntCtrlShiftA);
        t.assertEqual(1, cntCommandCtrlShiftA);
        t.assertEqual(1, cntCommandCtrlAltShiftA);

        key.unbind('a');
        key.unbind('shift+a');
        key.unbind('ctrl+shift+a');
        key.unbind('command+ctrl+shift+a');
        key.unbind('command+ctrl+alt+shift+a');

        keydown(65); keyup(65);
        keydown(KEYS.shift); keydown(65, [16]); keyup(65); keyup(KEYS.shift);
        keydown(KEYS.shift); keydown(KEYS.ctrl); keydown(65, [16, 17]); keyup(65); keyup(KEYS.shift); keyup(KEYS.ctrl);
        keydown(KEYS.command); keydown(KEYS.shift); keydown(KEYS.ctrl);
        keydown(65, [91, 16, 17]); keyup(65);
        keyup(KEYS.shift); keyup(KEYS.ctrl); keyup(KEYS.command);
        keydown(KEYS.alt); keydown(KEYS.command); keydown(KEYS.shift); keydown(KEYS.ctrl);
        keydown(65, [18, 91, 16, 17]); keyup(65);
        keyup(KEYS.shift); keyup(KEYS.ctrl); keyup(KEYS.command); keyup(KEYS.alt);

        t.assertEqual(1, cntA);
        t.assertEqual(1, cntShiftA);
        t.assertEqual(1, cntCtrlShiftA);
        t.assertEqual(1, cntCommandCtrlShiftA);
        t.assertEqual(1, cntCommandCtrlAltShiftA);
      },

      testUnbindWithModifiersMultiple: function(t){
        var cntA = 0, cntShiftA = 0, cntCtrlShiftA = 0;
        key('a', function(){ cntA++ });
        key('shift+a', function(){ cntShiftA++ });
        key('ctrl+shift+a', function(){ cntCtrlShiftA++ });

        keydown(65); keyup(65);
        keydown(KEYS.shift); keydown(65, [16]); keyup(65); keyup(KEYS.shift);
        keydown(KEYS.shift); keydown(KEYS.ctrl); keydown(65, [16, 17]); keyup(65); keyup(KEYS.shift); keyup(KEYS.ctrl);

        t.assertEqual(1, cntA);
        t.assertEqual(1, cntShiftA);
        t.assertEqual(1, cntCtrlShiftA);

        key.unbind('a, shift+a, ctrl+shift+a');

        keydown(65); keyup(65);
        keydown(KEYS.shift); keydown(65, [16]); keyup(65); keyup(KEYS.shift);
        keydown(KEYS.shift); keydown(KEYS.ctrl); keydown(65, [16, 17]); keyup(65); keyup(KEYS.shift); keyup(KEYS.ctrl);

        t.assertEqual(1, cntA);
        t.assertEqual(1, cntShiftA);
        t.assertEqual(1, cntCtrlShiftA);
      },

      testUnbindWithKeysMultiple: function(t){
        var cntSlashF = 0;
        key('/, f', function(){ cntSlashF++ });

        keydown(191); keyup(191);
        keydown(70); keyup(70);

        t.assertEqual(2, cntSlashF);

        key.unbind('/, f');

        keydown(191); keyup(191);
        keydown(70); keyup(70);

        t.assertEqual(2, cntSlashF);
      },

      testFancyModifierKeys: function(t){
        var sequence = '';
        key('⌃+a', function(){ sequence += 'a' });
        key('⌥+b', function(){ sequence += 'b' });
        key('⇧+c', function(){ sequence += 'c' });
        key('⌘+d', function(){ sequence += 'd' });

        keydown(KEYS.ctrl); keydown(65, [17]); keyup(65); keyup(KEYS.ctrl);
        keydown(KEYS.option); keydown(66, [18]); keyup(66); keyup(KEYS.option);
        keydown(KEYS.shift); keydown(67, [16]); keyup(67); keyup(KEYS.shift);
        keydown(KEYS.command); keydown(68, [91]); keyup(68); keyup(KEYS.command);

        t.assertEqual('abcd', sequence);
      },

      testNonAlphanumericKeys: function(t){
        var sequence = '', character,
          keys = { ',': 188, '.': 190, '/': 191,
            '`': 192, '-': 189, '=': 187,
            ';': 186, '\'': 222,
            '[': 219, ']': 221, '\\': 220 };

        for(character in keys){
          key(character, (function(character){
            return function(){ sequence += character }
          })(character));
          keydown(keys[character]); keyup(keys[character]);
        }

        t.assertEqual(',./`-=;\'[]\\', sequence);
      },

      testFKeys: function(t) {
        var sequence = '';
        for(var i=1;i<=19;i++) {
          key('f' + i , function(){ sequence += '' + i });
        }

        for(var i=1;i<=19;i++) {
          keydown(111 + i);
          keyup(111 + i);
        }

        t.assertEqual('12345678910111213141516171819', sequence);
      },

      testNamedSpecialKeys: function(t){
        // TODO
      },

      testEventCancelling: function(t){
        // TODO
      },

      testEventArguments: function(t){
        // TODO
      },

      testModifierBooleans: function(t){
        // TODO
      },

      testScoping: function(t){
        var sequence = '';

        key('a', function(){ sequence += 'a' });
        key('b', function(){ sequence += 'b' });
        key('b', 'capital', function(){ sequence += 'B' });

        keydown(65); keyup(65);
        key.setScope('capital');
        keydown(66); keyup(66);
        keydown(65); keyup(65);
        key.setScope('unknown');
        keydown(66); keyup(66);
        key.setScope('all');
        keydown(65); keyup(65);

        t.assertEqual('abBaba', sequence);

        // it should not call second callback after changing scope in one of callbacks
        var sequence = '';

        key('c', 'a', function(){ sequence += 'a'; key.setScope('b');  });
        key('c', 'b', function(){ sequence += 'b'; });

        key.setScope('a');
        keydown(67); keyup(67);
        keydown(67); keyup(67);
        key.setScope('all');

        t.assertEqual('ab', sequence);
      },

      testDeleteScope: function(t){
        var sequence = '';

        key('a', function(){ sequence += 'a' });
        key('b', function(){ sequence += 'b' });
        key('a', 'capital', function(){ sequence += 'A' });
        key('b', 'capital', function(){ sequence += 'B' });

        keydown(65); keyup(65);
        keydown(66); keyup(66);
        key.setScope('capital');
        keydown(65); keyup(65);
        keydown(66); keyup(66);
        key.deleteScope('capital');
        keydown(65); keyup(65);
        keydown(66); keyup(66);

        t.assertEqual('abaAbBab', sequence);
      },

      testUnbindWithScope: function(t){
        var cntIssues = 0, cntFiles = 0;
        key('a', 'issues', function(){ cntIssues++ });
        key('a', 'files', function(){ cntFiles++ });

        key.setScope('issues');
        keydown(65);
        keyup(65);
        t.assertEqual(1, cntIssues);
        key.unbind('a', 'issues');
        keydown(65);
        keyup(65);
        t.assertEqual(1, cntIssues);

        key.setScope('files');
        keydown(65);
        keyup(65);
        t.assertEqual(1, cntFiles);
        key.unbind('a', 'files');
        keydown(65);
        keyup(65);
        t.assertEqual(1, cntFiles);
        t.assertEqual(1, cntIssues);
      },

      testDoesntFireOnUserInputElements: function(t){
        // TODO
      },

      testCallsFilterFunction: function(t){
        var oldFilter = key.filter, filterCalls = 0, keyCalls = 0;

        key.filter = function(event){
          filterCalls++;
          return false;
        }

        key('a', function(){ keyCalls++; });
        keydown(65); keyup(65);

        t.assertEqual(1, filterCalls);
        t.assertEqual(0, keyCalls);

        key.filter = oldFilter;
        keydown(65); keyup(65);

        t.assertEqual(1, filterCalls);
        t.assertEqual(1, keyCalls);
      },

      testIsPressed: function (t) {
        keydown(65);
        t.assertTrue(key.isPressed(65));
        t.assertTrue(key.isPressed("a"));
        keyup(65);

        keydown(66); keyup(66);
        t.assertFalse(key.isPressed(66));

        keydown(38);
        t.assertTrue(key.isPressed("up"));
        keyup(38);
      },

      testGetPressedKeyCodes: function (t) {
        keydown(65); keydown(KEYS.shift);
        var pressedKeys = key.getPressedKeyCodes();
        var otherKeys = key.getPressedKeyCodes();
        t.assertTrue(pressedKeys.indexOf(65) >= 0);
        t.assertTrue(pressedKeys.indexOf(16) >= 0);
        t.assertTrue(pressedKeys !== otherKeys);
        keyup(65); keyup(KEYS.shift);
      },

      testNoConflict: function(t) {
        var k = key.noConflict();
        t.assertEqual(undefined, window.key);
        var called = false;
        k('a', function() {
          called = true;
        });
        keydown(65); keyup(65);
        t.assertEqual(called, true);
        window.key = k;
      },

      testScopeChangeDuringFilter: function(t) {
        var oldFilter = key.filter
          , scope1Pressed = 0
          , scope2Pressed = 0;

        key('a', 'scope1', function() {
          ++scope1Pressed;
        });

        key('a', 'scope2', function() {
          ++scope2Pressed;
        });

        key.filter = function() {
          key.setScope('scope1');
          return true;
        };

        keydown(65);
        keyup(65);
        t.assertTrue(scope1Pressed === 1);

        key.filter = function() {
          key.setScope('scope2');
          return true;
        };

        keydown(65);
        keyup(65);
        t.assertTrue(scope1Pressed === 1);
        t.assertTrue(scope2Pressed === 1);

        key.filter = oldFilter;
        key.setScope();
      }
    });
  </script>
</body>
</html>
